using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace GuavaData
{
    public class Audit : SemanticTransform
    {
        List<string> tables;
        Dictionary<string, List<string>> tableColumns;
        string addColumn;
        string deleteColumn;
        Dictionary<string, List<Guava>> deprecatedColumns;
        // Dictionary<string, string> deprecatedTables;

        DateTime now;

        public List<string> Tables
        {
            get { return tables; }
            set { tables = value; }
        }

        public Audit()
        {
            tables = new List<string>();
            tableColumns = new Dictionary<string, List<string>>();
            addColumn = "added";
            deleteColumn = "deleted";
            deprecatedColumns = new Dictionary<string, List<Guava>>();
        }

        public Audit(List<string> tablesToAudit, string addColumn, string deleteColumn):this()
        {
            this.tables = tablesToAudit;
            this.addColumn = addColumn;
            this.deleteColumn = deleteColumn;
            if (addColumn == deleteColumn)
                throw new Exception("Audit: cannot name the add and delete columns to the same name.");
        }

        public override void BuildUnits(string table, List<SemanticUnit> ls)
        {
            if (this.tables.Contains(table))
            {
                ls.Add(new AddColumnUnit(addColumn, new GuavaData.Domain.DateTime()));
                ls.Add(new AddColumnUnit(deleteColumn, new GuavaData.Domain.DateTime()));
                foreach (Guava g in deprecatedColumns[table])
                    ls.Add(new DeprecatedColumnUnit(g));
            }
        }

        public override System.Data.DataSet Process(Transaction o)
        {
            // Save off the current time so that all elements in the transaction use the same one
            now = DateTime.Now;
            return base.Process(o);
        }

        internal override void Expand(AddColumn ac, Command c)
        {
            // If not one of the tables we are interested in, punt
            if (!(this.tables.Contains(ac.Table)))
                return;

            // Two things to determine: does the column collide with the add/delete column, and does it clash with a deprecated column

            if (ac.Column == this.addColumn || ac.Column == this.deleteColumn)
                throw new DDLSideEffectException("Audit operator already tries to add a column named " + ac.Column + ".");
        }

        internal override void Expand(AddTable at, Command c)
        {
            // If not one of the tables we are interested in, punt
            if (!(this.tables.Contains(at.Table)))
                return;

            this.tableColumns[at.Table] = new List<string>(at.Columns);
            at.AddColumn(this.addColumn, new GuavaData.Domain.DateTime(), true);
            at.AddColumn(this.deleteColumn, new GuavaData.Domain.DateTime(), true);
            
            if (this.deprecatedColumns.ContainsKey(at.Table))
                foreach (Guava g in this.deprecatedColumns[at.Table])
                    at.AddColumn(g.GName, g.GDomain, false);
            
            at.EnforcePrimaryKey = false;
        }

        internal override void Expand(AlterColumnRename acr, Command c)
        {
            // If not one of the tables we are interested in, punt
            if (!(this.tables.Contains(acr.Table)))
                return;

            if (acr.NewName == this.addColumn || acr.NewName == this.deleteColumn)
                throw new DDLSideEffectException("Audit operator already tries to add a column named " + acr.NewName + ".");

            // Just in case we get a rename on the audit column, update the internals
            if (acr.OldName == this.addColumn)
                this.addColumn = acr.NewName;
            else if (acr.OldName == this.deleteColumn)
                this.deleteColumn = acr.NewName;
            else
            {
                this.tableColumns[acr.Table].Remove(acr.OldName);
                this.tableColumns[acr.Table].Add(acr.NewName);
            }
            if (this.deprecatedColumns.ContainsKey(acr.OldName))
            {
                this.deprecatedColumns[acr.NewName] = this.deprecatedColumns[acr.OldName];
                this.deprecatedColumns.Remove(acr.OldName);
            }
        }

        internal override void Expand(AlterElementRename aer, Command c)
        {
            base.Expand(aer, c);

            // Create the following statements, in order:
            // - Update the deleted column to the now value for all elements that have the old element value
            // - Add the old element's value to the list of deprecated element values
            // - Propagate the 
        }

        internal override void Expand(AlterTableRename atr, Command c)
        {
            // If not one of the tables we are interested in, punt
            if (!(this.tables.Contains(atr.OldName)))
            {
                if (this.tables.Contains(atr.NewName))
                    throw new DDLSideEffectException("Audit operator already tries to add a column named " + atr.NewName + ".");

                return;
            }

            // Adjust internals
            this.tables.Remove(atr.OldName);
            if (!(this.tables.Contains(atr.NewName)))
                this.tables.Add(atr.NewName);
        }

        internal override void Expand(DropColumn dc, Command c)
        {
            base.Expand(dc, c);
        }

        internal override void Expand(DropElement de, Command c)
        {
            base.Expand(de, c);
        }

        internal override void Expand(DropTable dt, Command c)
        {
            base.Expand(dt, c);
        }

        internal override void Expand(Delete d, Command c)
        {
            RowConstant rc = new RowConstant(new string[] { deleteColumn }, new Domain.Domain[] { new Domain.DateTime() }, new object[] { now });
            Update u = new Update(d.Table, new string[] { deleteColumn }, rc);
            for (int i = 0; i < d.Columns.Count; i++)
                u.AddCondition(d.Columns[i], d.Conditions[i]);
            c.root = u;
        }

        internal override void Expand(Insert i, Command c)
        {
            i.Child.Visit(this, c);

            RowConstant rc = new RowConstant(new string[] { this.addColumn }, new Domain.Domain[] { new Domain.DateTime() }, new object[] { now });
            CrossJoin cj = new CrossJoin(i.Child, rc);
            i.NotifyChange(i.Child, cj);
            i.Columns.Add(this.addColumn);
        }

        internal override void Expand(TableRef tr, Command c)
        {
            base.Expand(tr, c);
        }

        internal override void Expand(Update u, Command c)
        {
            base.Expand(u, c);
        }

        public override List<XElement> ToXElement()
        {
            XElement x = new XElement("Transform", new XAttribute("type", "Audit"), 
                new XAttribute("addColumn", addColumn),
                new XAttribute("deleteColumn",deleteColumn));

            for (int i = 0; i < tables.Count; i++)
                x.Add(new XElement(tables[i]));

            List<XElement> xList = new List<XElement>();
            if (!(this.Child is SqlProvider))
                xList = ((Transform)this.Child).ToXElement();
            xList.Add(x);
            return xList;
        }
        new public static Audit FromXElement(XElement x)
        {
            List<String> tables = new List<String>();
            String addCol = x.Attribute("addColumn").Value;
            String delCol = x.Attribute("deleteColumn").Value;
            foreach (var tab in x.Elements())
                tables.Add(tab.Name.LocalName);
            if (!(addCol == null) && !(delCol == null) && tables.Count > 0)
                return new Audit(tables, addCol, delCol);
            else
                throw new Exception("Error while loading Audit from XML");
        }

    }
}
